package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.thread.AppThreadLocalUtil;
import com.heima.common.cache.CacheService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.constants.BehaviorConstants;
import com.heima.common.freemarker.FreemarkerGenerator;
import com.heima.file.MinIoTemplate;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.vos.ArticleVisitStreamMess;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.search.pojos.SearchArticleVo;
import com.heima.model.user.pojos.ApUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;

    /**
     * 加载首页
     *
     * @param dto 入参
     * @return 返回结果
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto) {
        //minBehotTime=2063
        //publishTime < minBehotTime
        List<ApArticle> apArticles = apArticleMapper.loadArticle(dto, 1);
        return ResponseResult.okResult(apArticles);
    }

    @Override
    public ResponseResult load2(ArticleHomeDto dto) {

        if (dto == null) {
            log.warn("入参不能为空");
            return ResponseResult.okResult("");
        }

        // 获取频道id
        String tag = dto.getTag();

        // 查询缓存
        String hotArticleCache = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + tag);
        // 如果查询结果为空，走原来的流程
        if (StringUtils.isEmpty(hotArticleCache)) {
            return load(dto);
        }

        List<ApArticle> apArticles = JSON.parseArray(hotArticleCache, ApArticle.class);
        return ResponseResult.okResult(apArticles);
    }

    /**
     * 加载页面
     *
     * @param dto 入参
     * @return 返回结果
     */
    @Override
    public ResponseResult loadmore(ArticleHomeDto dto) {
        //publishTime < minBehotTime
        List<ApArticle> apArticles = apArticleMapper.loadArticle(dto, 1);
        return ResponseResult.okResult(apArticles);
    }

    /**
     * 刷新页面
     *
     * @param dto 入参
     * @return 返回结果
     */
    @Override
    public ResponseResult loadnew(ArticleHomeDto dto) {
        //publishTime > maxBehotTime
        List<ApArticle> apArticles = apArticleMapper.loadArticle(dto, 2);
        return ResponseResult.okResult(apArticles);
    }

    @Autowired
    private ApArticleContentMapper apArticleContentMapper;

    @Autowired
    private FreemarkerGenerator freemarkerGenerator;

    @Autowired
    private MinIoTemplate minIoTemplate;

    @Autowired
    private ApArticleConfigMapper apArticleConfigMapper;


    /**
     * 文章上传
     *
     * @param articleId 文章信息id
     */
    @Override
    @Async
    public void generate(Long articleId) {

        // 1. 查询ApArticleContent表，获得里面content数据
        LambdaQueryWrapper<ApArticleContent> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ApArticleContent::getArticleId, articleId);
        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(wrapper);
        if (apArticleContent == null) {
            log.warn("文章内容不存在");
            return;
        }

        // 2. 创建一个数据Map,把上一步content数据赋值到map的content字段上
        Map<String, Object> map = new HashMap<>(1);

        //将获取到的文章内容转换为集合
        map.put("content", JSON.parseArray(apArticleContent.getContent()));

        // 3. 调用Freemarker插件，生成html页面
        InputStream is = freemarkerGenerator.generate("article.ftl", map);

        // 4. 调用Minio插件，上传页面
        String url = minIoTemplate.uploadFile("", articleId + "html", is, "html");

        // 4-1 根据文章id获取信息
        ApArticle apArticle = apArticleMapper.selectById(articleId);
        if (apArticle == null) {
            log.warn("文章内容为空");
            return;
        }
        apArticle.setStaticUrl(url);
        // 5. 更新ApArticle,修改里面static_url字段
        apArticleMapper.updateById(apArticle);
    }

    /**
     * app文章保存
     *
     * @param dto 文章内容
     * @return 返回结果
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public ResponseResult saveArticle(ArticleDto dto) {

        // 1. dto判空
        if (dto == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_REQUIRE);
        }
        Long id = dto.getId();

        // 获取文章内容
        String content = dto.getContent();

        // 组装article表数据
        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(dto, apArticle);

        // 2. 判断dto里面的id是否存在
        if (id == null) {

            // 3. 不存在 -> 新增
            // 3-1 插入article表
            int insert = apArticleMapper.insert(apArticle);
            if (insert < 1) {
                return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "新增文章内容失败");
            }

            // 3-2 插入article——config表
            // 组装article——config表
            ApArticleConfig config = new ApArticleConfig();
            config.setIsDown(false);
            config.setIsDelete(false);
            config.setIsForward(true);
            config.setIsComment(true);
            config.setArticleId(apArticle.getId());

            int configInsertResult = apArticleConfigMapper.insert(config);
            if (configInsertResult < 0) {
                log.warn("文章配置表插入失败");
                throw new RuntimeException("文章配置表插入失败");
            }

            // 3-3 插入article——content表
            // 组装article——content表
            ApArticleContent articleContent = new ApArticleContent();
            articleContent.setArticleId(apArticle.getId());
            articleContent.setContent(content);

            int contentInsertResult = apArticleContentMapper.insert(articleContent);
            if (contentInsertResult < 1) {
                log.warn("文章内容表插入失败");
                throw new RuntimeException("文章内容表插入失败");
            }

            // 调用现成的方法，实现文章详情的生成和更新
            generate(apArticle.getId());

            return ResponseResult.okResult(apArticle.getId());

        } else {
            // 4. id存在 -> 修改

            // 4-1 修改article表
            int updateResult = apArticleMapper.updateById(apArticle);
            if (updateResult < 1) {
                return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "修改文章内容失败");
            }
            // 4-2 修改article——content表
            ApArticleContent contentUpdate = new ApArticleContent();
            contentUpdate.setContent(content);

            LambdaQueryWrapper<ApArticleContent> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(ApArticleContent::getArticleId, id);

            int contentUpdateResult = apArticleContentMapper.update(contentUpdate, wrapper);
            if (contentUpdateResult < 1) {
                log.warn("文章内容表修改失败");
                throw new RuntimeException("文章内容表修改失败");
            }

            // 调用详情生成
            generate(id);

            // 5. 将articleId返回回去
            return ResponseResult.okResult(id);
        }
    }

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    /**
     * 新建索引方法
     *
     * @param article 文章信息
     */
    @Override
    public void sendIndexUpdate(ApArticle article, String content) {

        if (article == null || StringUtils.isBlank(content)) {
            log.warn("入参不能为空 ");
            return;
        }

        SearchArticleVo vo = new SearchArticleVo();
        BeanUtils.copyProperties(article, vo);
        vo.setContent(content);

        kafkaTemplate.send("es.index.update", JSON.toJSONString(vo));

    }


    @Autowired
    private CacheService cacheService;

    /**
     * 加载文章行为-数据回显
     *
     * @param dto 文章id 作者id
     * @return 返回结果
     */
    @Override
    public ResponseResult loadArticleBehavior(ArticleInfoDto dto) {

        // 1. 入参判空
        if (dto == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_REQUIRE);
        }

        // 1-1 定义返回结果变量
        boolean islike = false;
        boolean isunlike = false;
        boolean iscollection = false;
        boolean isfollow = false;

        // 获取文章id
        Long articleId = dto.getArticleId();
        // 获取作者id
        Integer authorId = dto.getAuthorId();

        // 2. 获取当前登录用户
        ApUser user = AppThreadLocalUtil.getUser();
        if (user == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.AP_USER_DATA_NOT_EXIST);
        }
        Integer userId = user.getId();

        // 3. 喜欢行为(点赞)
        String like = (String) cacheService.hGet(BehaviorConstants.LIKE_BEHAVIOR + articleId, userId.toString());
        if (StringUtils.isNotBlank(like)) {
            islike = true;
        }

        // 4. 不喜欢的行为
        String unlike = (String) cacheService.hGet(BehaviorConstants.UN_LIKE_BEHAVIOR + articleId, userId.toString());
        if (StringUtils.isNotBlank(unlike)) {
            isunlike = true;
        }

        // 5. 是否收藏
        String collection = (String) cacheService.hGet(BehaviorConstants.COLLECTION_BEHAVIOR + articleId, userId.toString());
        if (StringUtils.isNotBlank(collection)) {
            iscollection = true;
        }

        // 6. 是否关注
        String follow = (String) cacheService.hGet(BehaviorConstants.APUSER_FOLLOW_RELATION + articleId, authorId.toString());
        if (StringUtils.isNotBlank(follow)) {
            isfollow = true;
        }

        // 7.封装返回结果
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("islike", islike);
        resultMap.put("isunlike", isunlike);
        resultMap.put("iscollection", iscollection);
        resultMap.put("isfollow", isfollow);
        return ResponseResult.okResult(resultMap);
    }


    /**
     * 更新数据库
     *
     * @param mess 入参
     */
    @Override
    public void updateScore(ArticleVisitStreamMess mess) {

        // 1. 入参判空
        if (mess == null) {
            log.warn("ArticleVisitStreamMess为空");
            return;
        }

        // 2. 更新文章四个数值
        ApArticle apArticle = updateAritcleHot(mess);
        if (apArticle == null) {
            log.warn("文章不存在");
            return;
        }

        // 3. 计算当前文章的总分
        Integer totalScore = compute(apArticle);

        // 4. 查询当前文章所在频道的热门缓存
        checkHot(apArticle, totalScore);

    }

    private void checkHot(ApArticle apArticle, Integer totalScore) {

        // 查询缓存中的新闻数据
        String hotScache = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());
        if (StringUtils.isEmpty(hotScache)) {
            // 直接写入
            log.info("缓存中没有数据，直接将当前文章加入缓存");
            addArticleToCache(apArticle,totalScore, new ArrayList<>());
            return;
        }

        // 将缓存中的新闻数据转换类型
        List<HotArticleVo> hotArticleVos = JSON.parseArray(hotScache, HotArticleVo.class);
        if (CollectionUtils.isEmpty(hotArticleVos)) {
            log.info("缓存中没有数据，直接将当前文章加入缓存");
            addArticleToCache(apArticle,totalScore, new ArrayList<>());
            return;
        }

        // 坚持当前文章是否存在于缓存之中
        boolean flag = false;

        // 遍历集合中的新闻
        for (HotArticleVo vo : hotArticleVos) {
            if (vo == null) {
                continue;
            }

            // 如果文章存在缓存中，更新总分值
            if (vo.getId().equals(apArticle.getId())) {
                vo.setScore(totalScore);
                flag = true;
                break;
            }
        }

        // 如果当前文章不存在于缓存中
        if (!flag) {

            // 获取缓存集合的长度
            int size = hotArticleVos.size();
            if (size < 30) {
                // 直接把当前内容写入到集合中
                addArticleToCache(apArticle,totalScore, hotArticleVos);
            } else {

                // 1. 获取30条里面，分值最小的那条
                HotArticleVo lastArticle = hotArticleVos.get(size - 1);

                // 获取最小文章的分值
                Integer lastScore = lastArticle.getScore();
                Integer currentScore = totalScore;

                // 2. 如果当前分值大于最小那条分值
                if (currentScore > lastScore) {

                    // 删除最小的那条
                    hotArticleVos.remove(size - 1);

                    // 将当前记录刷新到缓存中
                    addArticleToCache(apArticle,totalScore, hotArticleVos);

                }
            }
        } else {
            // 3. 将集合进行排序
            List<HotArticleVo> newHotAtrticleVoList = hotArticleVos.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());

            // 4. 将集合写入到频道缓存
            cacheService.set(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId(), JSON.toJSONString(newHotAtrticleVoList));

            // 5. 写入推荐频道缓存
            cacheService.set(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG, JSON.toJSONString(newHotAtrticleVoList));
        }
    }

    /**
     * 将文章更新到缓存中
     *
     * @param article 文章信息
     * @param voList  缓存集合列表
     */
    private void addArticleToCache(ApArticle article, Integer score, List<HotArticleVo> voList) {


        // 1. apArticle 转成 HotArticle
        HotArticleVo vo = new HotArticleVo();
        BeanUtils.copyProperties(article, vo);
        vo.setScore(score);

        // 2. add 添加到集合中
        voList.add(vo);

        // 3. 将集合进行排序
        List<HotArticleVo> newHotAtrticleVoList = voList.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());

        // 4. 将集合写入到频道缓存
        cacheService.set(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + article.getChannelId(), JSON.toJSONString(newHotAtrticleVoList));

        // 5. 写入推荐频道缓存
        cacheService.set(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG, JSON.toJSONString(newHotAtrticleVoList));
    }


    /**
     * 计算文章的总分
     *
     * @param apArticle 文章数据
     * @return 返回结果
     */
    private Integer compute(ApArticle apArticle) {

        Integer total = 0;

        // 1. 查询四个字段
        Integer likes = apArticle.getLikes();
        Integer views = apArticle.getViews();
        Integer comment = apArticle.getComment();
        Integer collection = apArticle.getCollection();

        if (likes != null) {
            total += likes;
        }

        if (views != null) {
            total += views * 3;
        }

        if (comment != null) {
            total += comment * 5;
        }

        if (collection != null) {
            total += collection * 8;
        }
        return total;
    }


    /**
     * 更新文章数据
     *
     * @param mess 传参文章数据
     */
    private ApArticle updateAritcleHot(ArticleVisitStreamMess mess) {

        // 1. 根据articleId查询出文章信息
        Long articleId = mess.getArticleId();
        ApArticle apArticle = apArticleMapper.selectById(articleId);
        if (apArticle == null) {
            log.warn("文章" + articleId + "不存在");
            return null;
        }

        // 2. 更新四个字段
        Integer likes = apArticle.getLikes();
        Integer views = apArticle.getViews();
        Integer comment = apArticle.getComment();
        Integer collection = apArticle.getCollection();

        apArticle.setLikes(likes == null ? mess.getLike() : likes + mess.getLike());
        apArticle.setViews(views == null ? mess.getView() : views + mess.getView());
        apArticle.setComment(comment == null ? mess.getComment() : comment + mess.getComment());
        apArticle.setCollection(collection == null ? mess.getCollect() : collection + mess.getCollect());

        // 3. updateByid更新文章数据
        apArticleMapper.updateById(apArticle);
        return apArticle;
    }
}


















